//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.Authentication
{
    using System;
    using System.Collections.ObjectModel;
    using System.DirectoryServices;
    using System.DirectoryServices.ActiveDirectory;
    using System.Globalization;
    using System.Security.Permissions;

    public class ADAuthentication : BaseAuthentication
    {
        private const string DISPLAYNAME = "displayName";

        private const string DISTINGUISHEDNAME = "distinguishedName";

        private const string SAMACCOUNTNAME = "sAMAccountName";

        private const string MEMBEROF = "memberof";

        private const string LDAPPROVIDER = "LDAP://";

        /// <summary>
        /// Get the User Credentials
        /// </summary>
        /// <param name="searchEntry">DirectoryEntry</param>
        /// <returns>ICredentials</returns>
        private static ICredentials GetUserCredentials(DirectoryEntry searchEntry)
        {
            ICredentials credentials = null;
            string userDomain = string.Empty;
            string distinguishedName = BaseAuthentication.GetPropertyValue(searchEntry, DISTINGUISHEDNAME);
            if (!string.IsNullOrEmpty(distinguishedName))
            {
                userDomain = BaseAuthentication.GetDomainFromDistinguishedName(distinguishedName);
            }
            string userName = BaseAuthentication.GetPropertyValue(searchEntry, SAMACCOUNTNAME);
            string displayName = BaseAuthentication.GetPropertyValue(searchEntry, DISPLAYNAME);

            credentials = new Credentials(string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", userDomain, userName), userName, displayName, userDomain, CredentialType.User);
            return credentials;
        }

        /// <summary>
        /// Get the Group Credentials
        /// </summary>
        /// <param name="searchEntry">DirectoryEntry</param>
        /// <returns>ICredentials</returns>
        private static ICredentials GetGroupCredentials(DirectoryEntry searchEntry)
        {
            ICredentials credentials = null;
            string userDomain = string.Empty;
            string distinguishedName = BaseAuthentication.GetPropertyValue(searchEntry, DISTINGUISHEDNAME);
            if (!string.IsNullOrEmpty(distinguishedName))
            {
                userDomain = BaseAuthentication.GetDomainFromDistinguishedName(distinguishedName);
            }

            string groupName = BaseAuthentication.GetPropertyValue(searchEntry, SAMACCOUNTNAME);
            credentials = new Credentials(userDomain + "\\" + groupName, groupName, userDomain, CredentialType.Group);
            return credentials;
        }

        /// <summary>
        /// Get the DirectoryEntry
        /// </summary>
        /// <param name="credentials">ICredentials</param>
        /// <returns>ICredentials</returns>
        private static DirectoryEntry GetObject(ICredentials credentials)
        {
            SearchResult searchResult = null;
            DirectoryContext context = null;
            DirectorySearcher directorySearcher = null;
            DirectoryEntry directoryEntry = null;
            try
            {
                //If domain name is not specified, search in forest
                if (String.IsNullOrEmpty(credentials.DomainName))
                {
                    context = new DirectoryContext(DirectoryContextType.Forest);
                    GlobalCatalog globalCatalog = GlobalCatalog.FindOne(context);
                    directorySearcher = globalCatalog.GetDirectorySearcher();
                }
                //Else search in the specified domain
                else
                {
                    DirectoryEntry entry = new DirectoryEntry(LDAPPROVIDER + credentials.DomainName);
                    directorySearcher = new DirectorySearcher(entry);
                }

                switch (credentials.CredentialType)
                {
                    case CredentialType.User:
                        directorySearcher.Filter = String.Format(CultureInfo.CurrentCulture, "(&(objectCategory=person)(sAMAccountName={0}))", credentials.UserName);
                        break;
                    case CredentialType.Group:
                        directorySearcher.Filter = "(&(objectClass=group)(|(cn=" + credentials.UserName + ")(dn=" + credentials.UserName + ")))";

                        break;
                }

                searchResult = directorySearcher.FindOne();
                if (searchResult != null)
                {
                    directoryEntry = searchResult.GetDirectoryEntry();
                }
            }
            catch (Exception)
            {
                //Consume and return null
            }

            return directoryEntry;
        }

        /// <summary>
        /// Get all Members of DirectoryEntry
        /// </summary>
        /// <param name="searchEntry">DirectoryEntry</param>
        /// <returns>Object</returns>
        private static Object GetAllMembers(DirectoryEntry searchEntry)
        {
            if (searchEntry.Properties[MEMBEROF].Value != null)
            {
                return searchEntry.Properties[MEMBEROF].Value;
            }

            return null;
        }

        /// <summary>
        /// Get the User(ICredentials) from the AD
        /// </summary>
        /// <param name="credentials">ICredentials</param>
        /// <returns>ICredentials</returns>
        [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
        public override ICredentials GetUser(ICredentials credentials)
        {
            ICredentials userCredential = null;
            DirectoryEntry userResult = ADAuthentication.GetObject(credentials);
            if (userResult != null)
            {
                //This is required as domain name may not be specified
                userCredential = ADAuthentication.GetUserCredentials(userResult);
            }

            return userCredential;
        }

        /// <summary>
        /// Get the Group(ICredentials) from the AD
        /// </summary>
        /// <param name="credentials">ICredentials</param>
        /// <returns>ICredentials</returns>
        [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
        public override ICredentials GetGroup(ICredentials credentials)
        {
            // string name = null;
            ICredentials groupCredential = null;
            DirectoryEntry userResult = ADAuthentication.GetObject(credentials);
            if (userResult != null)
            {
                groupCredential = GetGroupCredentials(userResult);
                groupCredential.DisplayName = credentials.UserName;
            }

            return groupCredential;
        }

        /// <summary>
        /// Get all Members of the User
        /// </summary>
        /// <param name="credentials">ICredentials</param>
        /// <returns>Collection<string> Groups</returns>
        [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
        public override Collection<string> GetGroups(ICredentials credentials)
        {
            Collection<string> groups = new Collection<string>();
            DirectoryEntry userResult = GetObject(credentials);
            if (userResult != null)
            {
                Object members = GetAllMembers(userResult);
                if (members != null)
                {
                    object[] groupNames = (object[])members;
                    if (groupNames != null)
                    {
                        for (int i = 0; i < groupNames.Length; i++)
                        {
                            groups.Add(groupNames[i].ToString().Split(',')[0].Substring(3));
                        }
                    }
                }
            }

            return groups;
        }
    }
}
